Skip to content

15-DI Container(3):如何重构已有的代码?

你好,我是徐昊。今天我们继续使用TDD的方式来实现注入依赖容器。

回顾代码与任务列表

到目前为止,我们的代码是这样的:

package geektime.tdd.di;

import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
 
import static java.util.Arrays.stream;
public class Context {
    private Map<Class<?>, Provider<?>> providers = new HashMap<>();
    
    public <Type> void bind(Class<Type> type, Type instance) {
        providers.put(type, (Provider<Type>) () -> instance);
    }
    
    public <Type, Implementation extends Type>
    void bind(Class<Type> type, Class<Implementation> implementation) {
        Constructor<?>[] injectConstructors = stream(implementation.getConstructors()).filter(c -> c.isAnnotationPresent(Inject.class))
                .toArray(Constructor<?>[]::new);
        if (injectConstructors.length > 1) throw new IllegalComponentException();
        if (injectConstructors.length == 0 && stream(implementation.getConstructors())
                .filter(c -> c.getParameters().length == 0).findFirst().map(c -> false).orElse(true))
            throw new IllegalComponentException();
        providers.put(type, (Provider<Type>) () -> {
            try {
                Constructor<Implementation> injectConstructor = getInjectConstructor(implementation);
                Object[] dependencies = stream(injectConstructor.getParameters())
                        .map(p -> get(p.getType()))
                        .toArray(Object[]::new);
                return injectConstructor.newInstance(dependencies);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    }
    
    private <Type> Constructor<Type> getInjectConstructor(Class<Type> implementation) {
        return (Constructor<Type>) stream(implementation.getConstructors())
                .filter(c -> c.isAnnotationPresent(Inject.class)).findFirst().orElseGet(() -> {
                    try {
                        return implementation.getConstructor();
                    } catch (NoSuchMethodException e) {
                        throw new RuntimeException(e);
                    }
                });
    }
    
    public <Type> Type get(Class<Type> type) {
        return (Type) providers.get(type).get();
    }
}

任务列表状态为:

  • 无需构造的组件——组件实例

  • 如果注册的组件不可实例化,则抛出异常

    • 抽象类
    • 接口
  • 构造函数注入

    • 无依赖的组件应该通过默认构造函数生成组件实例
    • 有依赖的组件,通过Inject标注的构造函数生成组件实例
    • 如果所依赖的组件也存在依赖,那么需要对所依赖的组件也完成依赖注入
    • 如果组件有多于一个Inject标注的构造函数,则抛出异常
    • 如果组件没有Inject标注的构造函数,也没有默认构造函数(新增任务)
    • 如果组件需要的依赖不存在,则抛出异常
    • 如果组件间存在循环依赖,则抛出异常
  • 字段注入

    • 通过Inject标注将字段声明为依赖组件
    • 如果组件需要的依赖不存在,则抛出异常
    • 如果字段为final则抛出异常
    • 如果组件间存在循环依赖,则抛出异常
  • 方法注入

    • 通过Inject标注的方法,其参数为依赖组件
    • 通过Inject标注的无参数方法,会被调用
    • 按照子类中的规则,覆盖父类中的Inject方法
    • 如果组件需要的依赖不存在,则抛出异常
    • 如果方法定义类型参数,则抛出异常
    • 如果组件间存在循环依赖,则抛出异常
  • 对Provider类型的依赖

    • 注入构造函数中可以声明对于Provider的依赖
    • 注入字段中可以声明对于Provider的依赖
    • 注入方法中可声明对于Provider的依赖
  • 自定义Qualifier的依赖

    • 注册组件时,可额外指定Qualifier
    • 注册组件时,可从类对象上提取Qualifier
    • 寻找依赖时,需同时满足类型与自定义Qualifier标注
    • 支持默认Qualifier——Named
  • Singleton生命周期

    • 注册组件时,可额外指定是否为Singleton
    • 注册组件时,可从类对象上提取Singleton标注
    • 对于包含Singleton标注的组件,在容器范围内提供唯一实例
    • 容器组件默认不是Single生命周期
  • 自定义Scope标注

    • 可向容器注册自定义Scope标注的回调

视频演示

让我们进入今天的部分:

思考题

如何实现循环依赖的检测?

欢迎把你的思考和想法分享在留言区,也欢迎你扫描详情页的二维码加入读者交流群。我们下节课再见!

最近更新